001 /* 002 * Copyright 2005 Stephen J. McConnell 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 013 * implied. 014 * 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019 package net.dpml.tools.tasks; 020 021 import java.io.File; 022 import java.io.OutputStream; 023 import java.io.FileOutputStream; 024 import java.io.OutputStreamWriter; 025 import java.io.Writer; 026 import java.io.IOException; 027 import java.net.URI; 028 import java.net.URL; 029 030 import net.dpml.lang.Version; 031 032 import net.dpml.library.Module; 033 import net.dpml.library.Resource; 034 import net.dpml.library.Type; 035 036 import net.dpml.transit.Artifact; 037 import net.dpml.transit.artifact.ArtifactNotFoundException; 038 import net.dpml.transit.link.ArtifactLinkManager; 039 040 import org.apache.tools.ant.BuildException; 041 import org.apache.tools.ant.Project; 042 import org.apache.tools.ant.types.FileSet; 043 import org.apache.tools.ant.taskdefs.Copy; 044 import org.apache.tools.ant.taskdefs.Checksum; 045 046 /** 047 * Execute the install phase. 048 * 049 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a> 050 * @version 1.1.1 051 */ 052 public class InstallTask extends GenericTask 053 { 054 /** 055 * Execute the project. 056 * @exception BuildException if a build errror occurs 057 */ 058 public void execute() throws BuildException 059 { 060 installDeliverables(); 061 } 062 063 private void installDeliverables() 064 { 065 Resource resource = getResource(); 066 String resourceVersion = resource.getVersion(); 067 boolean snapshot = "SNAPSHOT".equals( resourceVersion ); 068 boolean bootstrap = "BOOTSTRAP".equals( resourceVersion ); 069 boolean validation = resource.getBooleanProperty( "project.validation.enabled", false ); 070 boolean validate = !snapshot && !bootstrap && validation; 071 Type[] types = resource.getTypes(); 072 if( types.length == 0 ) 073 { 074 return; 075 } 076 077 final File deliverables = getContext().getTargetDeliverablesDirectory(); 078 for( int i=0; i < types.length; i++ ) 079 { 080 Type type = types[i]; 081 checkType( resource, type, validate ); 082 } 083 084 if( deliverables.exists() ) 085 { 086 log( "Installing deliverables from [" + deliverables + "]", Project.MSG_VERBOSE ); 087 final File cache = (File) getProject().getReference( "dpml.cache" ); 088 log( "To cache dir [" + cache + "]", Project.MSG_VERBOSE ); 089 try 090 { 091 final FileSet fileset = new FileSet(); 092 fileset.setProject( getProject() ); 093 fileset.setDir( deliverables ); 094 fileset.createInclude().setName( "**/*" ); 095 Module parent = resource.getParent(); 096 if( null == parent ) 097 { 098 copy( cache, fileset, true ); 099 } 100 else 101 { 102 final String group = parent.getResourcePath(); 103 final File destination = new File( cache, group ); 104 copy( destination, fileset, true ); 105 } 106 } 107 catch( Throwable e ) 108 { 109 final String error = 110 "Unexpected error while constructing ant fileset." 111 + "\nDeliverables dir: " + deliverables; 112 throw new BuildException( error, e ); 113 } 114 } 115 } 116 117 private void checkType( Resource resource, Type type, boolean validate ) 118 { 119 // 120 // Check that the project has actually built the resource 121 // type that it declares 122 // 123 124 String id = type.getID(); 125 String filename = getContext().getLayoutFilename( id ); 126 final File deliverables = getContext().getTargetDeliverablesDirectory(); 127 File group = new File( deliverables, id + "s" ); 128 File target = new File( group, filename ); 129 if( !target.exists() && !id.equalsIgnoreCase( "null" ) ) 130 { 131 final String error = 132 "Project [" 133 + resource 134 + "] declares that it produces the resource type [" 135 + id 136 + "] however no artifacts of that type are present in the target deliverables directory."; 137 throw new BuildException( error, getLocation() ); 138 } 139 140 // 141 // If the type declares an alias then construct a link 142 // and add the link to the deliverables directory as part of 143 // install process. 144 // 145 146 Version version = type.getVersion(); 147 if( null != version ) 148 { 149 try 150 { 151 Artifact artifact = resource.getArtifact( id ); 152 String uri = artifact.toURI().toASCIIString(); 153 String link = null; 154 if( Version.NULL_VERSION.equals( version ) ) 155 { 156 link = resource.getName() + "." + id + ".link"; 157 } 158 else 159 { 160 link = resource.getName() 161 + "-" 162 + version.getMajor() 163 + "." 164 + version.getMinor() 165 + "." 166 + id + ".link"; 167 } 168 File out = new File( group, link ); 169 boolean flag = true; 170 if( out.exists() ) 171 { 172 ArtifactLinkManager manager = new ArtifactLinkManager(); 173 URI enclosed = manager.getTargetURI( new URI( out.toURL().toString() ) ); 174 if( artifact.toURI().equals( enclosed ) ) 175 { 176 flag = false; 177 } 178 } 179 180 if( flag ) 181 { 182 final String message = 183 link.toString() 184 + "\n target: " 185 + uri.toString(); 186 log( message, Project.MSG_VERBOSE ); 187 out.createNewFile(); 188 final OutputStream output = new FileOutputStream( out ); 189 final Writer writer = new OutputStreamWriter( output ); 190 writer.write( uri ); 191 writer.close(); 192 output.close(); 193 } 194 } 195 catch( Exception e ) 196 { 197 final String error = 198 "Internal error while attempting to create a link for the resource type [" 199 + id 200 + "] in project [" 201 + resource 202 + "]."; 203 throw new BuildException( error, e, getLocation() ); 204 } 205 } 206 207 if( validate ) 208 { 209 validateType( resource, type, target ); 210 } 211 } 212 213 private void validateType( Resource resource, Type type, File target ) 214 { 215 String id = type.getID(); 216 try 217 { 218 Artifact artifact = resource.getArtifact( id ); 219 URL url = artifact.toURL(); 220 File file = (File) url.getContent( new Class[]{File.class} ); 221 if( file.exists() ) 222 { 223 log( "validating " + target.getName() ); 224 compare( file, target, id ); 225 } 226 } 227 catch( ArtifactNotFoundException anfe ) 228 { 229 // continue as there is nothing to compare with 230 } 231 catch( IOException ioe ) 232 { 233 final String error = 234 "IO error while attempting to cross-check resource type: " + id 235 + "\n" + ioe.toString(); 236 throw new BuildException( error, ioe, getLocation() ); 237 } 238 } 239 240 private void compare( File old, File target, String id ) 241 { 242 String oldValue = getChecksum( old ); 243 String newValue = getChecksum( target ); 244 if( !oldValue.equals( newValue ) ) 245 { 246 String path = getContext().getLayoutFilename( id ); 247 final String error = 248 "A versioned resource created in this build has a different MD5 signature " 249 + "compared to an existing resource of the same name in the cache directory. " 250 + "If the cached resource is a published resource a possibility exists that " 251 + "this build artifact will be introducing a modification to an existing published " 252 + "contract. If the resource has not been published then you can rebuild without " 253 + "deliverable validation. Otherwise, consider assigning an alternative " 254 + "(non-conflicting) version identifier." 255 + "\n" 256 + "\n\tProduced Type: " + path 257 + "\n\tCached Resource: " + getCanonicalPath( old ) 258 + "\n"; 259 throw new BuildException( error, getLocation() ); 260 } 261 } 262 263 private String getCanonicalPath( File file ) 264 { 265 try 266 { 267 return file.getCanonicalPath(); 268 } 269 catch( IOException e ) 270 { 271 final String error = 272 "Internal error while attempting to resolve a canonical path for the file: " + file; 273 throw new BuildException( error, e, getLocation() ); 274 } 275 } 276 277 private String getChecksum( File file ) 278 { 279 final String key = "checksum.property." + file.toString(); 280 final Checksum checksum = (Checksum) getProject().createTask( "checksum" ); 281 checksum.setTaskName( getTaskName() ); 282 checksum.setFile( file ); 283 checksum.setProperty( key ); 284 checksum.init(); 285 checksum.execute(); 286 return getProject().getProperty( key ); 287 } 288 289 /** 290 * Utility operation to copy a fileset to a destination directory. 291 * @param destination the destination directory 292 * @param fileset the fileset to copy 293 * @param preserve the preserve timestamp flag 294 */ 295 public void copy( final File destination, final FileSet fileset, boolean preserve ) 296 { 297 mkDir( destination ); 298 final Copy copy = (Copy) getProject().createTask( "copy" ); 299 copy.setTaskName( getTaskName() ); 300 copy.setPreserveLastModified( preserve ); 301 copy.setTodir( destination ); 302 copy.addFileset( fileset ); 303 copy.setOverwrite( true ); // required for filtered deliverables 304 copy.init(); 305 copy.execute(); 306 } 307 }